/*-----------------------------------------------------------------------------
	The contents of this file are subject to the Mozilla Public License
	Version 1.1 (the "License"); you may not use this file except in
	compliance with the License. You may obtain a copy of the License at
	http://www.mozilla.org/MPL/
	
	Software distributed under the License is distributed on an "AS IS"
	basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
	License for the specific language governing rights and limitations
	under the License.
	
	The Original Code is Fever Framework code.
	
	The Initial Developer of the Original Code is Romain Ecarnot.
	Portions created by Initial Developer are Copyright (C) 2006
	the Initial Developer. All Rights Reserved.
	
	Contributor(s):
	
	Unless required by applicable law or agreed to in writing, software
	distributed under the License is distributed on an "AS IS" BASIS,
	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	See the License for the specific language governing permissions and
	limitations under the License.
-----------------------------------------------------------------------------*/

import org.aswing.ASColor;
import org.aswing.ASTextFormat;
import org.aswing.border.EmptyBorder;
import org.aswing.border.LineBorder;
import org.aswing.BorderLayout;
import org.aswing.GridLayout;
import org.aswing.Insets;
import org.aswing.JButton;
import org.aswing.JLabel;
import org.aswing.JPanel;
import org.aswing.JTextField;

import fever.app.accelerator.Keyboard;
import fever.app.accelerator.KeyDefinition;
import fever.app.accelerator.ShortcutLocator;
import fever.app.accelerator.ShortcutMap;
import fever.app.local.Localisation;
import fever.log.FeverDebug;
import fever.utils.MathUtils;
import fever.utils.Stringifier;
import fever.utils.StringUtil;

import fvaswing.components.calculator.FvCalculatorResources;
import fvaswing.components.FvCalculator;
import fvaswing.FvAsWing;

/**
 * UI declaration for {@link fvaswing.components.FvCalculator} 
 * component.
 * 
 * <p>You can sets / retreives result using the {@link #getResult()} 
 * method or {@link #setValue()} method.
 * 
 * @author Romain Ecarnot
 */
class fvaswing.components.calculator.FvCalculatorUI extends JPanel
{
	//-------------------------------------------------------------------------
	// Private implementation
	//-------------------------------------------------------------------------
	
	private var _resultField : JTextField;
	private var _memoryLabel : JLabel;
	private var _memory : Number;
	
	private var _keysPanel : JPanel;

	private var _newEntry : Boolean;
	private var _decimalMode : Boolean;	private var _pendingDecimal : Boolean;
	
	private var _operator : String;
	private var _storeValue : Number;
	
	private var _backspace : JButton;
	private var _sqrt : JButton;
	
	private var _minWidth : Number;
	private var _type : Number;
	
	private var _keyMap : ShortcutMap;
	
	
	//-------------------------------------------------------------------------
	// Public Properties
	//-------------------------------------------------------------------------
	
	/** Display a classic calculator UI. */
	public static var CLASSIC : Number = 0;
	
	/** Display a complete calculator UI. */
	public static var COMPLETE : Number = 1;
		
	
	//-------------------------------------------------------------------------
	// Public API
	//-------------------------------------------------------------------------
	
	/**
	 * Constructor.
	 */
	public function FvCalculatorUI()
	{
		super( new BorderLayout( 5,5 ) );
		
		_type = CLASSIC;
		
		_keyMap = ShortcutLocator.getMap( FvCalculator.SHORTCUT_MAP );
		
		_configureUI();
		_handleResetInit();
	}
	
	/**
	 * Apply {@code n} number into result field.
	 */
	public function setValue( n : Number ) : Void
	{
		_resultField.setText( n.toString() );
		_createNewEntry();	
	}
	
	/**
	 * Returns current number in result field.
	 */
	public function getResult() : Number
	{
		return Number( _resultField.getText() );	
	}	
	
	/**
	 * Stores passed-in {@code n} value in memory.
	 */
	public function setMemory( n : Number ) : Void
	{
		_setMemoryValue( n );
	}
	
	/**
	 * Adds passed-in {@code n} value to memory.
	 */
	public function appendMemory( n : Number ) : Void
	{
		_setMemoryValue( _memory + n );	
	}
	
	/**
	 * Returns the current memory value.
	 */
	public function getMemory() : Number
	{
		return _memory;	
	}
	
	public function setCalculatorType( type : Number ) : Void
	{
		if( type != _type )
		{
			_type = type;
			_appendNewPadPanel();	
		}
	}
	
	public function getCalculatorType() : Number
	{
		return _type;	
	}
	
	/**
	 * Returns string representation.
	 */
	public function toString() : String
	{
		return Stringifier.parse( this );
	}
	
	
	//-------------------------------------------------------------------------
	// Private implementation
	//-------------------------------------------------------------------------
	
	private function _configureUI() : Void
	{
		setBorder( new EmptyBorder( null, new Insets( 5,5,5,5 ) ) );
		
		_minWidth = 35;
		
		var f : ASTextFormat = ASTextFormat.getEmptyASTextFormat();
		f.setAlign( ASTextFormat.RIGHT );
		
		_resultField = new JTextField( '0' );
		_resultField.setEditable( false );
		_resultField.setTextFormat( f );
		
		_memoryLabel = new JLabel( "", JLabel.CENTER );
		_memoryLabel.setBorder( new LineBorder( null, ASColor.BLACK, 1 ) );
		_memoryLabel.setPreferredWidth( _minWidth );
		
		_appendNewPadPanel();
		
		append( _buildControlPanel(), BorderLayout.NORTH );
		append( _createButton( '=', _handleResult, true, Keyboard.onKeyENTER ), BorderLayout.SOUTH );
	}
	
	private function _buildControlPanel() : JPanel
	{
		var p : JPanel = new JPanel( new BorderLayout( 5, 5 ) );
		p.append( _resultField, BorderLayout.CENTER );
		p.append( _buildFirstLine(), BorderLayout.SOUTH );
		return p;
	}
	
	private function _buildFirstLine() : JPanel
	{
		_backspace = _createButton( FvCalculatorResources.getInstance().backspace, _handleBackspace, true, Keyboard.onKeyBACKSPACE );
		Localisation.connectInstance( _backspace, _backspace.setText, 'fvaswing:calculator.backspace' );
		
		var line : JPanel = new JPanel( new GridLayout( 1, 3, 5, 5 ) );
		line.append( _backspace );
		line.append( _createButton( 'CE', _handleReset, true, Keyboard.onKeyDELETEKEY ) );
		line.append( _createButton( 'C', _handleResetInit, true ) );
		
		var p : JPanel = new JPanel( new BorderLayout( 5, 5 ) );
		p.append( _memoryLabel, BorderLayout.WEST );
		p.append( line, BorderLayout.CENTER );
		return p;
	}
	
	private function _appendNewPadPanel() : Void
	{
//		_keyMap.clear();
		
		_keysPanel.removeAll();
		
		var p : JPanel = ( _type == 1 ) ? _buildCompletePanel() : _buildLightPanel();
		append( p, BorderLayout.CENTER );
		
//		_keyMap.load();
	}
	
	private function _buildLightPanel() : JPanel
	{
		_keysPanel = new JPanel( new GridLayout( 4, 6, 5, 5 ) );
		
		_sqrt = _createButton( FvCalculatorResources.getInstance().sqrt, _handleSQRT, false );
		_sqrt.setPreferredWidth( _minWidth );
		_sqrt.setMinimumWidth( _minWidth );
		Localisation.connectInstance( _sqrt, _sqrt.setText, 'fvaswing:calculator.sqrt' );
		
		_keysPanel.append( _createButton( 'MC', _handleMemory, true ) );
		_keysPanel.append( _createButton( '7', _handleNum, false, Keyboard.onKeyPAD7 ) );
		_keysPanel.append( _createButton( '8', _handleNum, false, Keyboard.onKeyPAD8 ) );
		_keysPanel.append( _createButton( '9', _handleNum, false, Keyboard.onKeyPAD9 ) );
		_keysPanel.append( _createButton( '/', _handleOperator, true, Keyboard.onKeyDIVIDE ) );
		_keysPanel.append( _sqrt );
		
		_keysPanel.append( _createButton('MR', _handleMemory, true ) );
		_keysPanel.append( _createButton( '4', _handleNum, false, Keyboard.onKeyPAD4 ) );
		_keysPanel.append( _createButton( '5', _handleNum, false, Keyboard.onKeyPAD5 ) );
		_keysPanel.append( _createButton( '6', _handleNum, false, Keyboard.onKeyPAD6 ) );
		_keysPanel.append( _createButton( '*', _handleOperator, true, Keyboard.onKeyMULTIPLY ) );
		_keysPanel.append( _createButton( 'x^2', _handleOperation, false ) );
		
		_keysPanel.append( _createButton('MS', _handleMemory, true ) );
		_keysPanel.append( _createButton( '1', _handleNum, false, Keyboard.onKeyPAD1 ) );
		_keysPanel.append( _createButton( '2', _handleNum, false, Keyboard.onKeyPAD2 ) );
		_keysPanel.append( _createButton( '3', _handleNum, false, Keyboard.onKeyPAD3 ) );
		_keysPanel.append( _createButton( '-', _handleOperator, true, Keyboard.onKeySUBSTRACT ) );
		_keysPanel.append( _createButton( 'x^3', _handleOperation, false ) );
		
		_keysPanel.append( _createButton('M+', _handleMemory, true ) );
		_keysPanel.append( _createButton( '0', _handleNum, false, Keyboard.onKeyPAD0 ) );
		_keysPanel.append( _createButton( '+/-', _handleOperation, false ) );
		_keysPanel.append( _createButton( '.', _handleDot, false, Keyboard.onKeyDECIMAL ) );
		_keysPanel.append( _createButton( '+', _handleOperator, true, Keyboard.onKeyADD ) );
		_keysPanel.append( _createButton( 'x^y', _handleOperator, true ) );
		
		return _keysPanel;
	}
	
	private function _buildCompletePanel() : JPanel
	{
		_keysPanel = new JPanel( new GridLayout( 4, 8, 5, 5 ) );
		
		_sqrt = _createButton( FvCalculatorResources.getInstance().sqrt, _handleSQRT, false );
		_sqrt.setPreferredWidth( _minWidth );
		_sqrt.setMinimumWidth( _minWidth );
		Localisation.connectInstance( _sqrt, _sqrt.setText, 'fvaswing:calculator.sqrt' );
		
		_keysPanel.append( _createButton( 'MC', _handleMemory, true ) );
		_keysPanel.append( _createButton( '7', _handleNum, false, Keyboard.onKeyPAD7 ) );
		_keysPanel.append( _createButton( '8', _handleNum, false, Keyboard.onKeyPAD8 ) );
		_keysPanel.append( _createButton( '9', _handleNum, false, Keyboard.onKeyPAD9 ) );
		_keysPanel.append( _createButton( '/', _handleOperator, true, Keyboard.onKeyDIVIDE ) );
		_keysPanel.append( _sqrt );
		_keysPanel.append( _createButton( '1/x', _handleOperation, false ) );
		_keysPanel.append( _createButton( 'cos', _handleOperation, false ) );
		
		_keysPanel.append( _createButton('MR', _handleMemory, true ) );
		_keysPanel.append( _createButton( '4', _handleNum, false, Keyboard.onKeyPAD4 ) );
		_keysPanel.append( _createButton( '5', _handleNum, false, Keyboard.onKeyPAD5 ) );
		_keysPanel.append( _createButton( '6', _handleNum, false, Keyboard.onKeyPAD6 ) );
		_keysPanel.append( _createButton( '*', _handleOperator, true, Keyboard.onKeyMULTIPLY ) );
		_keysPanel.append( _createButton( 'x^2', _handleOperation, false ) );
		_keysPanel.append( _createButton( 'ln', _handleOperation, false ) );
		_keysPanel.append( _createButton( 'sin', _handleOperation, false ) );
		
		_keysPanel.append( _createButton('MS', _handleMemory, true ) );
		_keysPanel.append( _createButton( '1', _handleNum, false, Keyboard.onKeyPAD1 ) );
		_keysPanel.append( _createButton( '2', _handleNum, false, Keyboard.onKeyPAD2 ) );
		_keysPanel.append( _createButton( '3', _handleNum, false, Keyboard.onKeyPAD3 ) );
		_keysPanel.append( _createButton( '-', _handleOperator, true, Keyboard.onKeySUBSTRACT ) );
		_keysPanel.append( _createButton( 'x^3', _handleOperation, false ) );
		_keysPanel.append( _createButton( 'log', _handleOperation, false ) );
		_keysPanel.append( _createButton( 'tan', _handleOperation, false ) );
		
		_keysPanel.append( _createButton('M+', _handleMemory, true ) );
		_keysPanel.append( _createButton( '0', _handleNum, false, Keyboard.onKeyPAD0 ) );
		_keysPanel.append( _createButton( '+/-', _handleOperation, false ) );
		_keysPanel.append( _createButton( '.', _handleDot, false, Keyboard.onKeyDECIMAL ) );
		_keysPanel.append( _createButton( '+', _handleOperator, true, Keyboard.onKeyADD ) );
		_keysPanel.append( _createButton( 'x^y', _handleOperator, true ) );
		_keysPanel.append( _createButton( 'n!', _handleOperation, false ) );
		_keysPanel.append( _createButton( 'pi', _handleOperation, false ) );
		
		return _keysPanel;
	}

	private function _createButton( label : String, callback : Function, specialKey : Boolean, shortcut : KeyDefinition ) : JButton
	{
		var b : JButton = new JButton( label );
		b.addActionListener( callback, this );
		b.setForeground( ( specialKey ) ? ASColor.RED : ASColor.BLUE );
		
		if( shortcut )
		{
			_keyMap.unregister( shortcut );
			_keyMap.registerCustomType( FvAsWing.AWSHORTCUT, shortcut, b );
		}
		
		return b;
	}
	
	private function _createNewEntry() : Void
	{
		_newEntry = true;
		_decimalMode = false;
		_pendingDecimal = false;
	}
	
	private function _getDot() : String
	{
		if( _decimalMode && _pendingDecimal )
		{
			_pendingDecimal = false;
			return '.';
		}
		return '';
	}
	
	private function _setMemoryValue( n : Number ) : Void
	{
		_memory = n;
		_memoryLabel.setToolTipText( n.toString() );	
	}
	
	private function _resetMemory( ) : Void
	{
		_memoryLabel.setText();	
		_memoryLabel.setToolTipText();
		_memory = 0;
	}
	
	private function _handleNum( source : JButton ) : Void
	{
		if( _newEntry )
		{
			_resultField.setText( source.getText() );
			_newEntry = false;
		}
		else
		{
			_resultField.appendText( _getDot() + source.getText() );
		}
	}
	
	private function _handleDot( source : JButton ) : Void
	{
		if( !_decimalMode && _resultField.getText().indexOf(".") == -1 )
		{
			_decimalMode = true;
			_pendingDecimal = true;
			_newEntry = false;
		}
	}
	
	private function _handleReset( source : JButton ) : Void
	{
		_resultField.setText( '0' );
		_createNewEntry();
	}
	
	private function _handleResetInit( source : JButton ) : Void
	{
		_handleReset( source );
		
		_storeValue = Number.NaN;
		_operator = null;
		_resetMemory();
	}
	
	private function _handleBackspace( source : JButton ) : Void
	{
		var tx : String = _resultField.getText();
		if( tx != '0' && tx.length > 1 )
		{
			tx = tx.substr( 0, tx.length - 1 );
			_resultField.setText( tx );	
		}
		else if ( tx.length == 1 )
		{
			_handleReset( source );
		}
	}
	
	private function _handleResult( ) : Void
	{
		FeverDebug.INFO( 'Calculate calculator operation' );
		
		var newValue : Number = Number( _resultField.getText() );
		var result : Number;
		
		switch ( _operator )
		{
   			case '+' :
       			result = _addOperator( newValue );
       			break;
    		case '-' :
       			result = _substractOperator( newValue );
       			break;
    		case '*' :
       			result = _multiplyOperator( newValue );
       			break;
    		case '/' :
       			result = _divideOperator( newValue );
       			break;
       		case 'x^y' :
       			result = _powerOperator( newValue );
       			break;
       		default : 
       			result = newValue;
		}
		
		_resultField.setText( result.toString() );
		_storeValue = result;
		
		_newEntry = true;
		_operator = null;
	}
	
	private function _handleOperation( source : JButton ) : Void
	{
		var currentString : String = _resultField.getText();
		var currentNumber : Number = Number( currentString );
		var operation : String = source.getText();
		var result : String;
		
		switch ( operation )
		{
   			case '+/-' :
       			if( StringUtil.isStartingWith( currentString, '-' ) )
       				result = currentString.substr( 1 );
       			else
       				result = '-' + currentString;	
       			break;
    		case '1/x' :
       			result = String( 1 / currentNumber );
       			break;
    		case 'sqrt' :
       			result = String( Math.sqrt( currentNumber ) );
       			break;
       		case 'x^2' :
       			result = String( Math.pow( currentNumber, 2 ) );
       			break;
       		case 'x^3' :
       			result = String( Math.pow( currentNumber, 3 ) );
       			break;
       		case 'cos' :
       			result = String( Math.cos( currentNumber ) );
       			break;
       		case 'sin' :
       			result = String( Math.sin( currentNumber ) );
       			break;
       		case 'tan' :
       			result = String( Math.tan( currentNumber ) );
       			break;
       		case 'pi' :
       			result = Math.PI.toString();
       			break;
       		case 'ln' :
       			result = Math.log( currentNumber ).toString();
       			break;
       		case 'log' :
       			result = ( Math.log( currentNumber ) / Math.log( 10 ) ).toString();
       			break;
       		case 'n!' :
       			if( currentNumber <= 200 ) result = MathUtils.factorial( currentNumber ).toString();
       			else result = Number.POSITIVE_INFINITY.toString();
       			break;
       		default : 
       			result = currentString;
		}
		
		_resultField.setText( result );
		_createNewEntry();
	}
	
	private function _handleSQRT( source : JButton ) : Void
	{
		_resultField.setText( String( Math.sqrt( Number( _resultField.getText() ) ) ) );
		_createNewEntry();	
	}
	
	private function _handleMemory( source : JButton ) : Void
	{
		var memoryOp : String = source.getText();
		
		switch ( memoryOp )
		{
   			case 'MC' :
       			_resetMemory();
       			break;
    		case 'MR' :
       			_resultField.setText( _memory.toString() );
       			break;
    		case 'MS' :
       			_memoryLabel.setText( 'M' );
       			_setMemoryValue( Number( _resultField.getText() ) );
       			break;
       		case 'M+' :
       			var currentValue : Number = Number( _resultField.getText() );
       			_setMemoryValue( _memory + currentValue );
		}
		
		_createNewEntry();
	}
	
	private function _handleOperator( source : JButton ) : Void
	{
		_handleResult( source );
		
		_operator = source.getText();
		_createNewEntry();
	}
	
	private function _addOperator( value : Number ) : Number
	{
		return ( !isNaN( _storeValue ) ) ? _storeValue += value : value;	
	}
	
	private function _substractOperator( value : Number ) : Number
	{
		return ( !isNaN( _storeValue ) ) ? _storeValue -= value : value;
	}
	
	private function _multiplyOperator( value : Number ) : Number
	{
		return ( !isNaN( _storeValue ) ) ? _storeValue *= value : value;
	}
	
	private function _divideOperator( value : Number ) : Number
	{
		return ( !isNaN( _storeValue ) ) ? _storeValue /= value : value;
	}
	
	private function _powerOperator( value : Number ) : Number
	{
		return ( !isNaN( _storeValue ) ) ? Math.pow( _storeValue, value ) : value;
	}
}